﻿using Tnelab.Miniblink;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text.Json;
using Tnelab.Common;
using System.IO.Compression;

namespace Tnelab.WebApp
{
    public class MiniblinkWebBrowser : IWebBrowser
    {
        CursorType _cursorInfoType;
        public IWebWindow? HostWindow { get; protected set; }
        protected bool IsCloseed = false;
        protected Dictionary<string, Func<string, Stream>> _schemeHandlerDic { get; } = new Dictionary<string, Func<string, Stream>>();
        protected Dictionary<string, Func<string, Stream>> SchemeHandlerDic { get; } = new Dictionary<string, Func<string, Stream>>();
        protected Action<Action>? UIInvoke { get; private set; }
        protected IWebView WebView { get;  private set; }

        public event EventHandler<TitleChangedArgs>? TitleChanged;
        public event EventHandler<JsCallEventArgs>? JsCall;
        public event EventHandler<EventArgs>? WebViewIsReady;
        public event EventHandler<EventArgs>? DOMContentLoaded;
        public event EventHandler<PaintEventArg>? Paint;
        public WebBrowserType BrowserType { get => this.WebView.IsVip?WebBrowserType.MBVip:WebBrowserType.MBWke; }

        protected virtual void OnPaint(object sender,PaintEventArg arg)
        {
            this.Paint?.Invoke(sender, arg);
        }
        object _jsCallLock=new object();
        protected virtual void OnJsCall(object sender,JsCallEventArgs arg)
        {
            JsCall?.Invoke(this, arg);

            Task.Run(() => {
                lock (_jsCallLock)
                {
                    while (arg.Result == null)
                    {
                        Task.Yield();
                    }
                    if (!this.IsCloseed)
                    {
                        this.UIInvoke(() =>
                        {
                            this.WebView.ResponseJsCallNative(arg.CallId, arg.MessageId, JsonSerializer.Serialize(arg.Result));
                        });
                    }
                }

            }, new CancellationTokenSource(10 * 1000).Token);

        }
        protected virtual void OnWebViewReady()
        {
            WebViewIsReady?.Invoke(this, new EventArgs());
        }
        protected void OnDOMContentLoaded(object? sender, EventArgs e)
        {
            this.DOMContentLoaded?.Invoke(this, e);
        }
        protected void OnTitleChanged(object? sender,TitleChangedArgs e)
        {
            this.TitleChanged?.Invoke(this, e);
        }

        private void AddSchemeHandlerTo(string scheme, Func<string, Stream> handler)
        {
            SchemeHandlerDic.Add(scheme.ToLower(), handler);
        }
        public void AddSchemeHandler(string scheme, Func<string, Stream> handler)
        {
            if (WebView != null)
            {
                AddSchemeHandlerTo(scheme, handler);
            }
            else
            {
                _schemeHandlerDic.Add(scheme, handler);
            }
        }

        public Task<string> CallJsAsync(string script)
        {
            if (this.WebView.IsVip)
            {
                return this.WebView.RunJsAsync(script);
            }
            else
            {
                TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
                Task.Run(() =>
                {
                    this.HostWindow.UIInvoke(() =>
                    {
                        var r = this.WebView.RunJs(script);
                        tcs.SetResult(r);
                    });
                });
                return tcs.Task;
            }
        }

        public void Dispose()
        {
            if(WebView==null) 
                return;
            WebView.Dispose();
        }

        public void LoadUrl(string url)
        {
            this.WebView?.LoadUrl(url);
        }

        public void OnHostWindowCreated(IWebWindow parent)
        {
            this.HostWindow = parent;
            this.UIInvoke = this.HostWindow.UIInvoke;
            this.WebView = WebViewFactory.Create(parent.Handle);
            int old = Win32Api.GetWindowLong(this.HostWindow.Handle, Win32Api.GWL_EXSTYLE);
            Win32Api.SetWindowLong(this.HostWindow.Handle, Win32Api.GWL_EXSTYLE, old | Win32Api.WS_EX_LAYERED);
            this.WebView.Transparent = true;
            if (WebApplication.DebugEnable)
            {
                Task.Run(async () =>
                {
                    var ver = "20220405";
                    var debugDirName = $"debug_miniblink_{ver}";
                    if (!Directory.Exists(debugDirName))
                    {
                        Directory.CreateDirectory(debugDirName);
                        var front_endUrl = $"http://miniblink.x4tools.com/{ver}/front_end.zip";
                        using var httpClient = new HttpClient();
                        using var front_endStream=await httpClient.GetStreamAsync(front_endUrl);
                        using var front_endZipFileStream = File.OpenWrite($"{debugDirName}\\front_end.zip");
                        var retByteCount = 0;
                        var bytes = new byte[4 * 1024];
                        do
                        {
                            retByteCount=front_endStream.Read(bytes, 0, bytes.Length);
                            front_endZipFileStream.Write(bytes, 0, retByteCount);
                        }
                        while (retByteCount > 0);
                        front_endZipFileStream.Close();
                        front_endZipFileStream.Dispose();
                        ZipFile.ExtractToDirectory($"{debugDirName}\\front_end.zip", $"{debugDirName}");
                        File.Delete($"{debugDirName}\\front_end.zip");
                    }
                    this.UIInvoke(() => { 
                        this.WebView.SetDebugConfig("showDevTools", $"file:///{Environment.CurrentDirectory}/{debugDirName}/front_end/inspector.html");
                    });
                });                
            }
            foreach (var sh in _schemeHandlerDic)
            {
                AddSchemeHandlerTo(sh.Key, sh.Value);
            }
            _schemeHandlerDic.Clear();
            this.WebView.TitleChanged += WebView_TitleChanged;
            this.WebView.DocumentReady += WebView_DocumentReady;
            this.WebView.JsCallNative += WebView_JsCallNative;
            this.WebView.LoadUrlBegin += WebView_LoadUrlBegin;
            this.WebView.PaintUpdated += WebView_PaintUpdated;
            this.WebView.PaintBitUpdated += WebView_PaintBitUpdated;
            Win32Api.RECT outRect;
            Win32Api.GetWindowRect(this.HostWindow.Handle, out outRect);
            this.WebView.SetPosition(outRect.left, outRect.top);
            this.WebView.Resize(outRect.right - outRect.left, outRect.bottom - outRect.top);
            this.OnWebViewReady();
        }

        private void WebView_TitleChanged(object sender, Tnelab.Miniblink.TitleChangedEventArgs e)
        {
            this.OnTitleChanged(sender, new TitleChangedArgs(e.Title));
        }

        private void WebView_DocumentReady(object sender, DocumentReadyEventArgs e)
        {
            this.OnDOMContentLoaded(sender,e);
        }

        private void WebView_PaintUpdated(object sender, PaintUpdatedEventArgs e)
        {
            var arg = new PaintEventArg();
            arg.Hdc = e.Hdc;
            this.OnPaint(sender, arg);
        }

        private void WebView_PaintBitUpdated(object sender, PaintBitUpdatedEventArgs e)
        {
            //var bitmap = new Bitmap(this.HostWindow.Width, this.HostWindow.Height);
            //var datas = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
            //Marshal.Copy(e.Buffer, 0, datas.Scan0, e.Buffer.Length);
            //bitmap.UnlockBits(datas);
            //var arg = new PaintEventArg();
            //arg.Bitmap = bitmap;
            //this.OnPaint(sender,arg);
        }

        private void WebView_JsCallNative(object sender, JsCallNativeEventArgs e)
        {
            var args = new JsCallEventArgs(e.CallId,e.MessageId, e.Message);
            this.OnJsCall(sender,args);
        }

        private void WebView_LoadUrlBegin(object? sender, LoadUrlBeginEventArgs e)
        {
            var uri = new Uri(e.Url);
            var url = e.Url;
            if (url.ToLower().StartsWith("use://webapp"))
            {
                url = $"use://script/webapp.js?Name={this.HostWindow.Name}&WebViewPath={this.GetType().Assembly.GetName().Name}";
            }
            else if (url.ToLower().StartsWith("service://"))
            {
                var pageUri=new Uri(this.HostWindow.Url);
                url = $"{url}?Pre={pageUri.Scheme}://{pageUri.Host}:{pageUri.Port}";
            }
            var scheme = uri.Scheme;
            var path = url.Remove(0, scheme.Length + 3);
            if (!SchemeHandlerDic.ContainsKey(scheme))
            {
                return;
            }
            e.DataStream = SchemeHandlerDic[scheme]($"{scheme}://{path}");
        }
        (int, int, int, uint) GetMouseMsgInfo(IntPtr lParam, IntPtr wParam)
        {
            var x = Win32Api.LOWORD(lParam);
            var y = Win32Api.HIWORD(lParam);
            var delta = Win32Api.HIWORD(wParam);
            var flags_ = Win32Api.LOWORD(wParam);
            uint flags = 0;
            if ((wParam.ToInt64() & Win32Api.MK_CONTROL) > 0)
                flags |= (int)MouseFlags.CONTROL;
            if ((wParam.ToInt64() & Win32Api.MK_SHIFT) > 0)
                flags |= (int)MouseFlags.SHIFT;
            if ((wParam.ToInt64() & Win32Api.MK_LBUTTON) > 0)
                flags |= (int)MouseFlags.LBUTTON;
            if ((wParam.ToInt64() & Win32Api.MK_MBUTTON) > 0)
                flags |= (int)MouseFlags.MBUTTON;
            if ((wParam.ToInt64() & Win32Api.MK_RBUTTON) > 0)
                flags |= (int)MouseFlags.RBUTTON;
            return (x, y, delta, flags);
        }
        void OnMouseWheel(IntPtr lParam, IntPtr wParam)
        {
            var (x, y, delta, flags) = GetMouseMsgInfo(lParam, wParam);
            this.WebView.FireMouseWheelEvent(x, y, delta, flags);
        }
        void onCursorChange()
        {
            _cursorInfoType = (CursorType)this.WebView.GetCursorType();
        }
                bool setCursorInfoTypeByCache(IntPtr hWnd)
        {
            Win32Api.RECT rc;
            Win32Api.GetClientRect(hWnd, out rc);
            Win32Api.POINT pt = new Win32Api.POINT();
            Win32Api.GetCursorPos(ref pt);
            Win32Api.ScreenToClient(this.HostWindow.Handle, ref pt);
            if (!Win32Api.PtInRect(ref rc, pt))
                return false;

            IntPtr hCur = IntPtr.Zero;
            switch (_cursorInfoType)
            {
                case CursorType.Pointer:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32512);//
                    break;
                case CursorType.IBeam:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32513);
                    break;
                case CursorType.Hand:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32649);
                    break;
                case CursorType.Wait:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32514);
                    break;
                case CursorType.Help:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32651);
                    break;
                case CursorType.EastResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32644);
                    break;
                case CursorType.NorthResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32645);
                    break;
                case CursorType.SouthWestResize:
                case CursorType.NorthEastResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32643);
                    break;
                case CursorType.SouthResize:
                case CursorType.NorthSouthResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32645);
                    break;
                case CursorType.NorthWestResize:
                case CursorType.SouthEastResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32642);
                    break;
                case CursorType.WestResize:
                case CursorType.EastWestResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32644);
                    break;
                case CursorType.NorthEastSouthWestResize:
                case CursorType.NorthWestSouthEastResize:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32646);
                    break;
                default:
                    hCur = Win32Api.LoadCursorW(IntPtr.Zero, 32512);
                    break;
            }

            if (hCur != IntPtr.Zero)
            {
                Win32Api.SetCursor(hCur);
                return true;
            }

            return false;
        }
        public int? OnHostWindowMessageProc(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam)
        {
            int? result = null;
            if (this.WebView != null && this.HostWindow?.Handle == hWnd)
            {
                switch (message)
                {
                    case 0x007f:
                        break;
                    case Win32Api.WM_SIZE:
                        Win32Api.RECT outRect;
                        Win32Api.GetWindowRect(this.HostWindow.Handle, out outRect);
                        this.WebView.SetPosition(outRect.left, outRect.top);
                        this.WebView.Resize(outRect.right - outRect.left, outRect.bottom - outRect.top);
                        result = 0;
                        break;
                    case Win32Api.WM_MOUSEWHEEL:
                        OnMouseWheel(lParam, wParam);
                        result = 0;
                        break;
                    case Win32Api.WM_LBUTTONDOWN:
                    case Win32Api.WM_LBUTTONUP:
                    case Win32Api.WM_MOUSEMOVE:
                    case Win32Api.WM_RBUTTONDOWN:
                    case Win32Api.WM_RBUTTONUP:
                    case Win32Api.WM_MBUTTONDOWN:
                    case Win32Api.WM_MBUTTONUP:
                        onCursorChange();
                        var (x, y, delta, flags) = GetMouseMsgInfo(lParam, wParam);
                        this.WebView.FireMouseEvent(message, x, y, flags);
                        if (message != Win32Api.WM_RBUTTONUP)
                        {
                            result = 0;
                        }
                        break;
                    case Win32Api.WM_KEYDOWN:
                        uint virtualKeyCode = (uint)wParam;
                        flags = 0;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_REPEAT) == Win32Api.KF_REPEAT)
                            flags |= (uint)KeyFlags.REPEAT;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_EXTENDED) == Win32Api.KF_EXTENDED)
                            flags |= (uint)KeyFlags.EXTENDED;
                        this.WebView.FireKeyDownEvent(virtualKeyCode, flags, false);
                        result = 0;
                        break;
                    case Win32Api.WM_KEYUP:
                        virtualKeyCode = (uint)wParam;
                        flags = 0;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_REPEAT) == Win32Api.KF_REPEAT)
                            flags |= (uint)KeyFlags.REPEAT;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_EXTENDED) == Win32Api.KF_EXTENDED)
                            flags |= (uint)KeyFlags.EXTENDED;
                        this.WebView.FireKeyUpEvent(virtualKeyCode, flags, false);
                        result = 0;
                        break;
                    case Win32Api.WM_CHAR:
                        uint charCode = (uint)wParam;
                        flags = 0;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_REPEAT) == Win32Api.KF_REPEAT)
                            flags |= (uint)KeyFlags.REPEAT;
                        if ((Win32Api.HIWORD(lParam) & Win32Api.KF_EXTENDED) == Win32Api.KF_EXTENDED)
                            flags |= (uint)KeyFlags.EXTENDED;
                        this.WebView.FireKeyPressEvent(charCode, flags, false);
                        result = 0;
                        break;
                    case Win32Api.WM_SETCURSOR:
                        this.WebView.FireWindowsMessage(Win32Api.WM_SETCURSOR, 0, 0, out var r);
                        setCursorInfoTypeByCache(this.HostWindow.Handle);
                        result = 0;
                        break;
                    case Win32Api.WM_SETFOCUS:
                        this.WebView.SetFocus();
                        break;
                    case Win32Api.WM_KILLFOCUS:
                        this.WebView.KillFocus();
                        break;
                    case Win32Api.WM_IME_STARTCOMPOSITION:
                        this.WebView.FireWindowsMessage((uint)message, (uint)wParam, (uint)lParam, out var rx);
                        result = 0;
                        break;
                }
            }
            return result;
        }
    }
}